package org.hs.phd.odi.bgsub;

import java.awt.Polygon;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hs.phd.common.simpleplayer.ImageSequenceFrame;
import org.hs.phd.odi.bgsub.vob.VisualObject;
import org.hs.phd.odi.bgsub.vob.VisualObjectClass;

import com.googlecode.javacpp.Loader;
import com.googlecode.javacv.cpp.opencv_core;
import com.googlecode.javacv.cpp.opencv_core.CvPoint;
import com.googlecode.javacv.cpp.opencv_core.CvSeq;
import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_imgproc;
import com.googlecode.javacv.cpp.opencv_video;

public class FGObjectDetector {

	private opencv_video.BackgroundSubtractorMOG2 backgroundModel;
	private IplImage foregroundImage;

	private int backgroundInitFrameCount;
	private final int framesNeededForBgInit;

	public FGObjectDetector(){
		this(0);
	}
	
	public FGObjectDetector(int framesNeededForBgInit){
		this.framesNeededForBgInit = framesNeededForBgInit;
		backgroundModel = new opencv_video.BackgroundSubtractorMOG2();
		backgroundInitFrameCount = 0;
	}

	public List<VisualObject> detectFGObjects(ImageSequenceFrame input) {
		if( foregroundImage == null ){
			foregroundImage = opencv_core.cvCreateImage(input.getFrameImage().cvSize(), opencv_core.IPL_DEPTH_8U, 1 );
			//opencv_core.cvSet(foregroundImage, opencv_core.cvScalarAll(255));
		}
		
		backgroundModel.apply(input.getFrameImage(), foregroundImage, 0.003);

		if( isReady() ){
			improveDetectedForeground();
			return detectForegroundObjects(input);
		}else{
			backgroundInitFrameCount++;
			return Collections.emptyList();
		}
	}
	
	private void improveDetectedForeground() {
		opencv_imgproc.GaussianBlur(foregroundImage, foregroundImage, opencv_core.cvSize(7, 7), 1.5, 1.5, opencv_imgproc.BORDER_DEFAULT);
		morphologicalOpen(foregroundImage,2);
		morphologicalClose(foregroundImage,5);
		opencv_imgproc.cvThreshold(foregroundImage, foregroundImage, 100, 255, opencv_imgproc.CV_THRESH_BINARY);
	}

	private void morphologicalClose(opencv_core.CvArr foreGroundImage, int n) {
		dilate(foreGroundImage, n);
		erode(foreGroundImage, n);
	}

	private void erode(opencv_core.CvArr foreGroundImage, int n) {
		opencv_imgproc.cvErode(foreGroundImage, foreGroundImage, null, n);
	}

	private void dilate(opencv_core.CvArr foreGroundImage, int n) {
		opencv_imgproc.cvDilate(foreGroundImage, foreGroundImage, null, n);
	}

	private void morphologicalOpen(opencv_core.CvArr foreGroundImage, int n) {
		erode(foreGroundImage, n);
		dilate(foreGroundImage, n);
	}

	
	public List<VisualObject> detectForegroundObjects(ImageSequenceFrame imageSequenceFrame){

		List<VisualObject> fgObjects = new ArrayList<VisualObject>();
		
	    opencv_core.CvMemStorage mem = opencv_core.CvMemStorage.create();
	    opencv_core.CvSeq contours = new opencv_core.CvSeq();

	    opencv_imgproc.cvFindContours(foregroundImage, mem, contours, Loader.sizeof(opencv_core.CvContour.class) , 
	    		opencv_imgproc.CV_RETR_EXTERNAL, opencv_imgproc.CV_CHAIN_APPROX_SIMPLE, opencv_core.cvPoint(0,0));

	    for (opencv_core.CvSeq ptr = contours; ptr != null; ptr = ptr.h_next()) {
	    	Polygon polygon = getCvSeqAsPolygon(ptr);
			VisualObject visualObject = new VisualObject( polygon, VisualObjectClass.UNKNOWN );
			
			fgObjects.add( visualObject );
	    }
	    
	    return fgObjects;
	}

	private Polygon getCvSeqAsPolygon(CvSeq ptr) {
	    Polygon p = new Polygon();
	    
	    for( int i=0; i < ptr.total(); ++i ){
			opencv_core.CvPoint point = new CvPoint(opencv_core.cvGetSeqElem(ptr, i)) ;
		    p.addPoint(point.x(), point.y());
		}
	    
	    return p;
	}

	public int getRemainingFramesForBGInitCompletion() {
		return framesNeededForBgInit - backgroundInitFrameCount;
	}

	public boolean isReady() {
		return getRemainingFramesForBGInitCompletion() == 0;
	}

}
